package network

import (
	"fmt"
	"net"
	"sync/atomic"
	"time"

	"gitee.com/doraemon1996/bunny/goroutine"
	"gitee.com/doraemon1996/bunny/structure"
	"gitee.com/doraemon1996/bunny/timer"
	"gitee.com/doraemon1996/bunny/uuid"
)

// UDP连接
type UDPConn struct {
	conn

	rawConn  *net.UDPConn // 底层UDP连接
	isListen bool         // 是否是Listen得到的连接
}

// 新建UDP连接
func NewUDPConn(ln Listener, rawConn *net.UDPConn, isListen bool) *UDPConn {
	udp := new(UDPConn)
	udp.id = uuid.GenUint64()
	udp.ln = ln
	udp.rawConn = rawConn
	udp.isListen = isListen
	return udp
}

// 获取连接类型
func (conn *UDPConn) Type() ConnectionType {
	return Connection_UDP
}

// 获取连接概况
func (conn *UDPConn) String() (str string) {
	if conn.isListen {
		str = fmt.Sprintf("UDPConn[%X][%s]", conn.id, conn.rawConn.LocalAddr().String())
	} else {
		str = fmt.Sprintf("UDPConn[%X][%s<-->%s]", conn.id, conn.rawConn.LocalAddr().String(), conn.rawConn.RemoteAddr().String())
	}
	return
}

// 获取底层连接
func (conn *UDPConn) RawConn() net.Conn {
	return conn.rawConn
}

// 是否是Listen返回的连接
func (conn *UDPConn) IsListen() bool {
	return conn.isListen
}

// 开始运行连接的消息处理逻辑
func (conn *UDPConn) Start(handler EventHandler) {
	if handler == nil {
		panic(ErrorEventHandlerInvalid)
	}

	conn.ctl.Start(func() {
		conn.eventHandler = handler

		// 开启定时任务
		if conn.crontabPeriod.Milliseconds() >= 1 {
			conn.crontabID = timer.Interval(conn.crontabPeriod, func() {
				conn.eventHandler.OnCrontab(conn)
			})
			if conn.crontabID == 0 {
				err := &networkError{ErrorType_Network, ErrorCrontabLuanchFailed}
				if conn.eventHandler.OnError(conn, err) {
					conn.Stop(err)
					return
				}
			}
		}

		conn.ctl.Add(2)
		goroutine.Submit(func() {
			err := conn.recvLoop()
			conn.ctl.Done()
			if conn.ctl.Opening() {
				conn.Stop(err)
			}
		})
		goroutine.Submit(func() {
			err := conn.sendLoop()
			conn.ctl.Done()
			if conn.ctl.Opening() {
				conn.Stop(err)
			}
		})
	})
}

// 停止并关闭连接
func (conn *UDPConn) Stop(err Error) {
	conn.ctl.Stop(func() {
		conn.rawConn.SetReadDeadline(time.Now())
		if conn.crontabID != 0 {
			timer.Remove(conn.crontabID)
			conn.crontabID = 0
		}

		conn.ctl.Wait()

		close(conn.sendChan)
		if conn.eventHandler != nil {
			conn.eventHandler.OnDisconnect(conn, err)
		}
		conn.rawConn.Close()
	})
}

// 发送消息
func (conn *UDPConn) Send(msg any) (err error) {
	if conn.ctl.Closed() {
		return &networkError{ErrorType_Network, ErrorConnectionClosed}
	}

	var (
		p           []byte            // 消息编码后的二进制数据
		customerErr error             // Encode()函数返回的错误
		maxSize     int               // 消息包大小上限
		buf         *structure.Buffer // 数据缓存
		packet      *UDPPacket        // UDP消息包
	)

	p, customerErr = conn.eventHandler.Encode(conn, msg)
	if customerErr != nil {
		err = &networkError{ErrorType_Customer, customerErr}
		return
	}

	if len(p) < 1 {
		return &networkError{ErrorType_Network, ErrorMessageBufferSizeTooShort}
	}

	maxSize = conn.writeLimit
	if maxSize > MaxUDPPacketSize {
		maxSize = MaxUDPPacketSize
	}
	if len(p) > maxSize {
		return &networkError{ErrorType_Network, ErrorUDPPacketSizeTooLarge}
	}

	packet = udpPacketPool.Get()

	switch val := msg.(type) {
	case UDPPacket:
		packet.PeerAddr = val.PeerAddr
	case *UDPPacket:
		packet.PeerAddr = val.PeerAddr
	}

	// Listen连接需要指定对端地址
	if conn.isListen && packet.PeerAddr == nil {
		return &networkError{ErrorType_Network, ErrorUDPPacketPeerAddrNotSpecify}
	}

	buf = bufferPool.Get()
	buf.Write(p)
	packet.Message = buf

	conn.sendChan <- packet

	return
}

// 从底层连接读取一次消息数据
func (conn *UDPConn) read(stream *structure.Buffer) (addr *net.UDPAddr, err Error) {
	var (
		buf           []byte // 读取到的二进制消息数据
		length        int    // 读取到的数据长度
		underlyingErr error  // net包返回的错误
	)

	defer func() {
		if val := recover(); val != nil {
			err = &networkError{ErrorType_Network, val}
		}
	}()

	buf = stream.WriteNextBegin(conn.readLimit)
	length, addr, underlyingErr = conn.rawConn.ReadFromUDP(buf)
	stream.WriteNextEnd(length)
	atomic.AddUint64(&conn.readBytes, uint64(length))

	if underlyingErr != nil {
		err = &networkError{ErrorType_Underlying, underlyingErr}
	}

	return
}

// 处理消息数据
func (conn *UDPConn) handle(stream *structure.Buffer, addr *net.UDPAddr) (err Error) {
	var (
		msg         any   // 解码得到的消息
		customerErr error // Decode()函数返回的错误
	)

	defer func() {
		if val := recover(); val != nil {
			err = &networkError{ErrorType_Customer, val}
		}
	}()

	msg, _, customerErr = conn.eventHandler.Decode(conn, &UDPPacket{Message: stream.Bytes(), PeerAddr: addr})
	if msg == nil {
		return
	}

	if customerErr != nil {
		err = &networkError{ErrorType_Customer, customerErr}
		return
	}

	conn.eventHandler.OnMessage(conn, msg)
	atomic.AddUint64(&conn.readPackages, 1)

	return
}

// 收消息循环
func (conn *UDPConn) recvLoop() (err Error) {
	var (
		stream *structure.Buffer // 从底层连接中读取处理的消息包数据
		addr   *net.UDPAddr      // 接受到消息的对端地址
	)

	stream = bufferPool.Get()
	defer bufferPool.Put(stream)

	for !conn.ctl.Closed() {
		stream.Reset()

		addr, err = conn.read(stream)
		if err != nil {
			// 发生错误时,只能退出循环或丢弃数据重新读取
			if !conn.ctl.Closed() && conn.eventHandler.OnError(conn, err) {
				break
			} else {
				continue
			}
		}

		err = conn.handle(stream, addr)
		if err != nil && !conn.ctl.Closed() && conn.eventHandler.OnError(conn, err) {
			break
		}
	}
	return
}

// 将消息数据写到底层连接
func (conn *UDPConn) write(buf *structure.Buffer, addr *net.UDPAddr) (err Error) {
	var (
		length        int
		underlyingErr error // net包返回的错误
	)

	defer func() {
		if val := recover(); val != nil {
			err = &networkError{ErrorType_Underlying, val}
		}
	}()

	// 使用 WriteMsgUDP() 写Socket时,如果UDP连接已经对端目标,参数addr必须为nil
	if !conn.isListen {
		addr = nil
	}

	length, _, underlyingErr = conn.rawConn.WriteMsgUDP(buf.Bytes(), nil, addr)
	if underlyingErr == nil {
		atomic.AddUint64(&conn.writeBytes, uint64(length))
		atomic.AddUint64(&conn.writePackages, 1)
	} else {
		err = &networkError{ErrorType_Underlying, underlyingErr}
	}

	return
}

// 发消息循环
func (conn *UDPConn) sendLoop() (err Error) {
	var (
		packet *UDPPacket        // 将要发送的消息包
		buf    *structure.Buffer // 消息数据
	)

	for !conn.ctl.Closed() {
		if len(conn.sendChan) > 0 {
			packet = (<-conn.sendChan).(*UDPPacket)
		}

		if packet != nil {
			buf = packet.Message.(*structure.Buffer)
			err = conn.write(buf, packet.PeerAddr)
			bufferPool.Put(buf)
			udpPacketPool.Put(packet)
			packet = nil

			if err != nil && !conn.ctl.Closed() && conn.eventHandler.OnError(conn, err) {
				break
			}
		}
	}
	return
}
